﻿using System;
using System.Collections.Generic;
using System.Reflection;
using System.Runtime.CompilerServices;

namespace MugenInjection.Interception.Infrastructure
{
    internal static class ProxyUtils
    {
        #region Fields

        private static readonly object Locker = new object();
        private static readonly Dictionary<Assembly, bool> CheckedAssembly = new Dictionary<Assembly, bool>();

        public const string DefultAssemblyNameInternal = "MugenInjectionProxyAssembly";

        #endregion

        #region Method

        internal static bool IsInterfacesAndNotDuplicated(Type[] interfaces)
        {
            var dictionary = new Dictionary<Type, object>();
            foreach (Type @interface in interfaces)
            {
                try
                {
                    dictionary.Add(@interface, null);
                }
                catch (Exception)
                {
                    throw new ArgumentException("Duplicate interface, type " + @interface);
                }
#if NETFX_CORE
                if (!@interface.GetTypeInfo().IsInterface)
#else
                if (!@interface.IsInterface)
#endif

                    return false;
            }
            return true;
        }

        internal static void CheckNotGenericTypeDefinition(Type type, string argumentName)
        {
#if NETFX_CORE
            if (type != null && type.GetTypeInfo().IsGenericTypeDefinition)
#else
            if (type != null && type.IsGenericTypeDefinition)
#endif
                throw new ArgumentException("You cannot specify a generic type definition.", argumentName);
        }

        internal static void CheckNotGenericTypeDefinition(IEnumerable<Type> types, string argumentName)
        {
            if (types == null)
                return;
            foreach (Type type in types)
                CheckNotGenericTypeDefinition(type, argumentName);
        }


        internal static void AssertValidType(Type target)
        {
#if NETFX_CORE
            if (target.GetTypeInfo().IsGenericTypeDefinition)
#else
            if (target.IsGenericTypeDefinition)
#endif
                throw new ArgumentException(string.Format("Type {0} is a generic type definition. Cannot create proxy for open generic types.", target.FullName));
            if (!IsPublic(target) && !IsAccessible(target))
                throw new ArgumentException(
                    string.Format(
                        "Type {0} is not visible to proxy. Cannot create proxy for types that are not accessible. Make the type public, or internal and mark your assembly with [assembly: InternalsVisibleTo(InterceptionExtensionUtils.ProxyAssemblyName)] attribute.",
                        target.FullName));
        }

        internal static void AssertValidType(IEnumerable<Type> targetTypes)
        {
            if (targetTypes == null)
                return;
            foreach (Type target in targetTypes)
                AssertValidType(target);
        }

        internal static void GetAllInterfaces(List<Type> interfaces, Type @interface)
        {
            if (@interface == null || interfaces.Contains(@interface)) return;
            interfaces.Add(@interface);
#if NETFX_CORE
            foreach (var i in @interface.GetTypeInfo().ImplementedInterfaces)
#else
            foreach (var i in @interface.GetInterfaces())
#endif
            {
                GetAllInterfaces(interfaces, i);
            }
        }

        private static bool IsAccessible(Type target)
        {
#if NETFX_CORE
            return IsInternal(target) && IsInternalToProxy(target.GetTypeInfo().Assembly);
#else
            return IsInternal(target) && IsInternalToProxy(target.Assembly);
#endif

        }

        private static bool IsPublic(Type target)
        {
#if NETFX_CORE
            return target.GetTypeInfo().IsPublic || (target.GetTypeInfo().IsNestedPublic && (target.DeclaringType.GetTypeInfo().IsPublic || IsAccessible(target.DeclaringType)));
#else
            return target.IsPublic || (target.IsNestedPublic && (target.DeclaringType.IsPublic || IsAccessible(target.DeclaringType)));
#endif
        }

        private static bool IsInternal(Type target)
        {
#if NONESTED
            bool isNested = target.DeclaringType != null;
#else
            bool isNested = target.IsNested;
#endif
#if NETFX_CORE
            bool flag = isNested && (target.GetTypeInfo().IsNestedAssembly || target.GetTypeInfo().IsNestedFamORAssem);
            return !target.GetTypeInfo().IsVisible && !isNested || flag;
#else
            bool flag = isNested && (target.IsNestedAssembly || target.IsNestedFamORAssem);
            return !target.IsVisible && !isNested || flag;
#endif
        }

        /// <summary>
        /// Determines whether this assembly has internals visible to proxy.
        /// </summary>
        /// <param name="asm">The assembly to inspect.</param>
        internal static bool IsInternalToProxy(Assembly asm)
        {
            lock (Locker)
            {
                bool isInternal;
                if (!CheckedAssembly.TryGetValue(asm, out isInternal))
                {
#if NETFX_CORE
                    var customAttributes = asm.GetCustomAttributes(typeof(InternalsVisibleToAttribute));
#else
                    var customAttributes = asm.GetCustomAttributes(typeof(InternalsVisibleToAttribute), true);
#endif
                    foreach (InternalsVisibleToAttribute visibleToAttribute in customAttributes)
                    {
                        if (visibleToAttribute.AssemblyName.Equals(InterceptionExtensionUtils.ProxyAssemblyName))
                        {
                            CheckedAssembly.Add(asm, true);
                            return true;
                        }
                    }
                    CheckedAssembly.Add(asm, false);
                }
                return isInternal;
            }
        }

        #endregion
    }
}